﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Mbs.Converters
{
    public interface IConverterManager
    {
        void AddConverterClass(Type converterClass);
        void AddConverterFunc(Func<Type, Type, MethodInfo> converterFunc);
        MethodInfo GetConverter(Type from, Type to);
    }

    public struct TypePair : IEquatable<TypePair>
    {
        public readonly Type From;
        public readonly Type To;
        readonly int HashCode;

        public TypePair(Type from, Type to)
        {
            From = from;
            To = to;

            HashCode = 397 * From.GetHashCode() + To.GetHashCode();
        }

        public override int GetHashCode()
        {
            return HashCode;
        }

        public bool Equals(TypePair other)
        {
            return HashCode == other.HashCode;
        }


        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (obj.GetType() != typeof(TypePair)) return false;
            return Equals((TypePair)obj);

        }

        public override string ToString()
        {
            return From.ToString() + " -> " + To.ToString();
        }
    }

    public class ConvertManager:IConverterManager
    {
    

        private Dictionary<TypePair, MethodInfo> _converterMethods = new Dictionary<TypePair, MethodInfo>();
        private List<Func<Type, Type, MethodInfo>> _converterFuncs = new List<Func<Type, Type, MethodInfo>>();

        public void AddConverterClass(Type converter)
        {
            const BindingFlags Flags = BindingFlags.Static | BindingFlags.Public;

            foreach (var method in converter.GetMethods(Flags))
            {
                var parameters = method.GetParameters();
                if (parameters.Length == 1 && method.ReturnType != typeof(void))
                    _converterMethods[new TypePair(parameters[0].ParameterType, method.ReturnType)] = method;
            }
        }

        public void AddConverterFunc(Func<Type, Type, MethodInfo> converterFunc)
        {
            _converterFuncs.Add(converterFunc);
        }

        public MethodInfo GetConverter(Type from, Type to)
        {
            if (from == null || to == null)
            {
                return null;
            }

            foreach (var func in ((IEnumerable<Func<Type, Type, MethodInfo>>)_converterFuncs).Reverse())
            {
                var result = func(from, to);
                if (result != null)
                {
                    return result;
                }
            }

            MethodInfo res = null;
            _converterMethods.TryGetValue(new TypePair (from,to), out res);
            return res;
        }
    }
}
